package com.design.behavioral;

/**
 * 模版方法模式：定义一个操作中的算法骨架，而将算法的一些步骤延迟到子类中，使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。少部分行为未知或与环境相关
 *
 * 模板方法（Template Method）模式包含以下主要角色：
 * * 抽象类（Abstract Class）：负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。
 *   * 模板方法：定义了算法的骨架，按某种顺序调用其包含的基本方法。
 *   * 基本方法：是实现算法各个步骤的方法，是模板方法的组成部分。基本方法又可以分为三种：
 *     * 抽象方法(Abstract Method) ：一个抽象方法由抽象类声明、由其具体子类实现。
 *     * 具体方法(Concrete Method) ：一个具体方法由一个抽象类或具体类声明并实现，其子类可以进行覆盖也可以直接继承。
 *     * 钩子方法(Hook Method) ：在抽象类中已经实现，包括用于判断的逻辑方法和需要子类重写的空方法两种。
 *       一般钩子方法是用于判断的逻辑方法，这类方法名一般为isXxx，返回值类型为boolean类型。
 *       钩子方法的引入使得子类可以控制父类的行为。
 * * 具体子类（Concrete Class）：实现抽象类中所定义的抽象方法和钩子方法，它们是一个顶级逻辑的组成步骤。
 */
public class TemplateMethodDemo {
    public static void main(String[] args) {
        StirFry baoCai = new ConcreteClass_BaoCai();
        StirFry caiXin = new ConcreteClass_CaiXin();

        baoCai.cookProcess();
        System.out.println("--------------------");
        caiXin.cookProcess();
    }
}

/**
 * 抽象类：负责给出一个算法的轮廓和骨架
 */
abstract class StirFry{
    public final void cookProcess(){
        //第一步：倒油
        this.pourOil();
        //第二步：热油
        this.heatOil();
        //第三步：倒蔬菜是不一样的
        this.pourVegetable();
        //第四步：倒调味料是不一样
        this.pourSauce();
        //第五步：翻炒是一样的
        this.fry();
    }

    //第一步：倒油
    public void pourOil(){
        System.out.println("倒油");
    }

    //第二步：热油是一样的，所以直接实现
    public void heatOil(){
        System.out.println("热油");
    }

    //第三步：倒蔬菜是不一样的（一个下包菜，一个是下菜心）
    public abstract void pourVegetable();

    //第四步：倒调味料是不一样
    public abstract void pourSauce();

    //第五步：翻炒是一样的，所以直接实现
    public void fry(){
        System.out.println("翻炒");
    }
}

/**
 * 包菜实现类
 */
class ConcreteClass_BaoCai extends StirFry{

    @Override
    public void pourVegetable() {
        System.out.println("倒入包菜");
    }

    @Override
    public void pourSauce() {
        System.out.println("放入包菜调料");
    }
}

/**
 * 菜心实现类
 */
class ConcreteClass_CaiXin extends StirFry{

    @Override
    public void pourVegetable() {
        System.out.println("倒入菜心");
    }

    @Override
    public void pourSauce() {
        System.out.println("放入菜心调料");
    }
}